Using Layout Templates

As we showed in Layout and Styling of Jupyter widgets multiple widgets can be arranged together using the flexible GridBox specification. However, use of the specification involves some understanding of CSS properties and may impose sharp learning curve. Here, we will describe layout templates built on top of GridBox that simplify creation of common widget layouts.


In [ ]:
# Utils widgets
from ipywidgets import Button, Layout, jslink, IntText, IntSlider

def create_expanded_button(description, button_style):
    return Button(description=description, button_style=button_style, layout=Layout(height='auto', width='auto'))

top_left_button = create_expanded_button("Top left", 'info')
top_right_button = create_expanded_button("Top right", 'success')
bottom_left_button = create_expanded_button("Bottom left", 'danger')
bottom_right_button = create_expanded_button("Bottom right", 'warning')

top_left_text = IntText(description='Top left', layout=Layout(width='auto', height='auto'))
top_right_text = IntText(description='Top right', layout=Layout(width='auto', height='auto'))
bottom_left_slider = IntSlider(description='Bottom left', layout=Layout(width='auto', height='auto'))
bottom_right_slider = IntSlider(description='Bottom right', layout=Layout(width='auto', height='auto'))

2x2 Grid

You can easily create a layout with 4 widgets arranged on 2x2 matrix using the TwoByTwoLayout widget:


In [ ]:
from ipywidgets import TwoByTwoLayout

TwoByTwoLayout(top_left=top_left_button,
               top_right=top_right_button,
               bottom_left=bottom_left_button,
               bottom_right=bottom_right_button)

If you don't define a widget for some of the slots, the layout will automatically re-configure itself by merging neighbouring cells


In [ ]:
TwoByTwoLayout(top_left=top_left_button,
               bottom_left=bottom_left_button,
               bottom_right=bottom_right_button)

You can pass merge=False in the argument of the TwoByTwoLayout constructor if you don't want this behavior


In [ ]:
TwoByTwoLayout(top_left=top_left_button,
               bottom_left=bottom_left_button,
               bottom_right=bottom_right_button,
               merge=False)

You can add a missing widget even after the layout initialization:


In [ ]:
layout_2x2 = TwoByTwoLayout(top_left=top_left_button,
                            bottom_left=bottom_left_button,
                            bottom_right=bottom_right_button)
layout_2x2

In [ ]:
layout_2x2.top_right = top_right_button

You can also use the linking feature of widgets to update some property of a widget based on another widget:


In [ ]:
app = TwoByTwoLayout(top_left=top_left_text, top_right=top_right_text,
                     bottom_left=bottom_left_slider, bottom_right=bottom_right_slider)
                     
link_left = jslink((app.top_left, 'value'), (app.bottom_left, 'value'))
link_right = jslink((app.top_right, 'value'), (app.bottom_right, 'value'))
app.bottom_right.value = 30
app.top_left.value = 25
app

You can easily create more complex layouts with custom widgets. For example, you can use bqplot Figure widget to add plots:


In [ ]:
import bqplot as bq
import numpy as np

In [ ]:
size = 100
np.random.seed(0)

x_data = range(size)
y_data = np.random.randn(size)
y_data_2 = np.random.randn(size)
y_data_3 = np.cumsum(np.random.randn(size) * 100.)

x_ord = bq.OrdinalScale()
y_sc = bq.LinearScale()

bar = bq.Bars(x=np.arange(10), y=np.random.rand(10), scales={'x': x_ord, 'y': y_sc})
ax_x = bq.Axis(scale=x_ord)
ax_y = bq.Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')

fig = bq.Figure(marks=[bar], axes=[ax_x, ax_y], padding_x=0.025, padding_y=0.025,
                layout=Layout(width='auto', height='90%'))

In [ ]:
from ipywidgets import FloatSlider

max_slider = FloatSlider(min=0, max=10, default_value=2, description="Max: ",
                         layout=Layout(width='auto', height='auto'))
min_slider = FloatSlider(min=-1, max=10, description="Min: ",
                         layout=Layout(width='auto', height='auto'))
app = TwoByTwoLayout(top_left=min_slider,
                     bottom_left=max_slider, 
                     bottom_right=fig,
                     align_items="center", 
                     height='700px')

jslink((y_sc, 'max'), (max_slider, 'value'))
jslink((y_sc, 'min'), (min_slider, 'value'))
jslink((min_slider, 'max'), (max_slider, 'value'))
jslink((max_slider, 'min'), (min_slider, 'value'))

max_slider.value = 1.5
app

AppLayout

AppLayout is a widget layout template that allows you to create an application-like widget arrangements. It consist of a header, a footer, two sidebars and a central pane:


In [ ]:
from ipywidgets import AppLayout, Button, Layout

In [ ]:
header_button = create_expanded_button('Header', 'success')
left_button = create_expanded_button('Left', 'info')
center_button = create_expanded_button('Center', 'warning')
right_button = create_expanded_button('Right', 'info')
footer_button = create_expanded_button('Footer', 'success')

In [ ]:
AppLayout(header=header_button,
          left_sidebar=left_button,
          center=center_button,
          right_sidebar=right_button,
          footer=footer_button)

However with the automatic merging feature, it's possible to achieve many other layouts:


In [ ]:
AppLayout(header=None,
          left_sidebar=None,
          center=center_button,
          right_sidebar=None,
          footer=None)

In [ ]:
AppLayout(header=header_button,
          left_sidebar=left_button,
          center=center_button,
          right_sidebar=right_button,
          footer=None)

In [ ]:
AppLayout(header=None,
          left_sidebar=left_button,
          center=center_button,
          right_sidebar=right_button,
          footer=None)

In [ ]:
AppLayout(header=header_button,
          left_sidebar=left_button,
          center=center_button,
          right_sidebar=None,
          footer=footer_button)

In [ ]:
AppLayout(header=header_button,
          left_sidebar=None,
          center=center_button,
          right_sidebar=right_button,
          footer=footer_button)

In [ ]:
AppLayout(header=header_button,
          left_sidebar=None,
          center=center_button,
          right_sidebar=None,
          footer=footer_button)

In [ ]:
AppLayout(header=header_button,
          left_sidebar=left_button,
          center=None,
          right_sidebar=right_button,
          footer=footer_button)

You can also modify the relative and absolute widths and heights of the panes using pane_widths and pane_heights arguments. Both accept a sequence of three elements, each of which is either an integer (equivalent to the weight given to the row/column) or a string in the format '1fr' (same as integer) or '100px' (absolute size).


In [ ]:
AppLayout(header=header_button,
          left_sidebar=left_button,
          center=center_button,
          right_sidebar=right_button,
          footer=footer_button,
          pane_widths=[3, 3, 1],
          pane_heights=[1, 5, '60px'])

Grid layout

GridspecLayout is a N-by-M grid layout allowing for flexible layout definitions using an API similar to matplotlib's GridSpec.

You can use GridspecLayout to define a simple regularly-spaced grid. For example, to create a 4x3 layout:


In [ ]:
from ipywidgets import GridspecLayout

grid = GridspecLayout(4, 3)

for i in range(4):
    for j in range(3):
        grid[i, j] = create_expanded_button('Button {} - {}'.format(i, j), 'warning')
grid

To make a widget span several columns and/or rows, you can use slice notation:


In [ ]:
grid = GridspecLayout(4, 3, height='300px')
grid[:3, 1:] = create_expanded_button('One', 'success')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'warning')
grid[3, 2] = create_expanded_button('Four', 'danger')
grid

You can still change properties of the widgets stored in the grid, using the same indexing notation.


In [ ]:
grid = GridspecLayout(4, 3, height='300px')
grid[:3, 1:] = create_expanded_button('One', 'success')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'warning')
grid[3, 2] = create_expanded_button('Four', 'danger')

grid

In [ ]:
grid[0, 0].description = "I am the blue one"

Note: It's enough to pass an index of one of the grid cells occupied by the widget of interest. Slices are not supported in this context.

If there is already a widget that conflicts with the position of the widget being added, it will be removed from the grid:


In [ ]:
grid = GridspecLayout(4, 3, height='300px')
grid[:3, 1:] = create_expanded_button('One', 'info')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'info')
grid[3, 2] = create_expanded_button('Four', 'info')

grid

In [ ]:
grid[3, 1] = create_expanded_button('New button!!', 'danger')

Note: Slices are supported in this context.


In [ ]:
grid[:3, 1:] = create_expanded_button('I am new too!!!!!', 'warning')

Creating scatter plots using GridspecLayout

In these examples, we will demonstrate how to use GridspecLayout and bqplot widget to create a multipanel scatter plot. To run this example you will need to install the bqplot package.

For example, you can use the following snippet to obtain a scatter plot across multiple dimensions:


In [ ]:
import bqplot as bq
import numpy as np
from ipywidgets import GridspecLayout, Button, Layout

n_features = 5
data = np.random.randn(100, n_features)
data[:50, 2] += 4 * data[:50, 0] **2
data[50:, :] += 4

A = np.random.randn(n_features, n_features)/5

data = np.dot(data,A)

scales_x = [bq.LinearScale() for i in range(n_features)]
scales_y = [bq.LinearScale() for i in range(n_features)]

gs = GridspecLayout(n_features, n_features)
for i in range(n_features):
    for j in range(n_features):
        
        if i != j:
            sc_x = scales_x[j]
            sc_y = scales_y[i]

            scatt = bq.Scatter(x=data[:, j], y=data[:, i], scales={'x': sc_x, 'y': sc_y}, default_size=1)

            gs[i, j] = bq.Figure(marks=[scatt], layout=Layout(width='auto', height='auto'),
                                 fig_margin=dict(top=0, bottom=0, left=0, right=0))
        else:
            sc_x = scales_x[j]
            sc_y = bq.LinearScale()
        
            hist = bq.Hist(sample=data[:,i], scales={'sample': sc_x, 'count': sc_y})
            
            gs[i, j] = bq.Figure(marks=[hist], layout=Layout(width='auto', height='auto'),
                                 fig_margin=dict(top=0, bottom=0, left=0, right=0))
gs

Style attributes

You can specify extra style properties to modify the layout. For example, you can change the size of the whole layout using the height and width arguments.


In [ ]:
AppLayout(header=None,
          left_sidebar=left_button,
          center=center_button,
          right_sidebar=right_button,
          footer=None,
          height="200px", width="50%")

The gap between the panes can be increase or decreased with grid_gap argument:


In [ ]:
AppLayout(header=None,
          left_sidebar=left_button,
          center=center_button,
          right_sidebar=right_button,
          footer=None,
          height="200px", width="50%",
          grid_gap="10px")

Additionally, you can control the alignment of widgets within the layout using justify_content and align_items attributes:


In [ ]:
from ipywidgets import Text, HTML
TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,
               bottom_right=bottom_right_button,
               justify_items='center',
               width="50%",
               align_items='center')

For other alignment options it's possible to use common names (top and bottom) or their CSS equivalents (flex-start and flex-end):


In [ ]:
TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,
               bottom_right=bottom_right_button,
               justify_items='center',
               width="50%",
               align_items='top')

Example

In this notebook you will find a full example using AppLayout.